package de.schule.barnimgymnasium.javafx.broadcaster;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import javafx.concurrent.Service;
import javafx.concurrent.Task;
import de.schule.barnimgymnasium.javafx.broadcaster.Network.IncomingObject;

public class Network extends Service<IncomingObject> {

	int COMMUNICATION_PORT = 10101;
	final int RECEIVE_BUFFER_LENGTH = 65536;
	final DatagramSocket socket;
	private final List<InterfaceAddress> broadcasterInterfaceAdresses = Network
			.getBroadcastInterfaceAddresses();

	public Network() throws SocketException {
		this.socket = new DatagramSocket(this.COMMUNICATION_PORT);
		this.socket.setBroadcast(true);
	}

	/** 
	 * Erstellt die Aufgabe, ein einzelnes rundgesendetes IP-Paket auf dem Kommunikationsport zu empfangen.
	 * @see javafx.concurrent.Service#createTask()
	 */
	@Override
	public Task<IncomingObject> createTask() {
		return new Task<IncomingObject>() {

			@Override
			protected IncomingObject call() {
				final byte[] buf = new byte[Network.this.RECEIVE_BUFFER_LENGTH];
				Object obj = null;
//				do {
					final DatagramPacket receivedPacket = new DatagramPacket(
							buf, buf.length);
					try {
						Network.this.socket.receive(receivedPacket);
						System.err.println("recv from "
								+ receivedPacket.getAddress());
						final ByteArrayInputStream bais = new ByteArrayInputStream(
								buf, 0, receivedPacket.getLength());
						obj = new ObjectInputStream(bais).readObject();
						return new IncomingObject(receivedPacket.getAddress(),
								obj);
					} catch (final ClassNotFoundException | IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
//				} while (true);
					return null;
			}
		};
	}

	/**
	 * Sendet ein Objekt über alle am Rechner angeschlossenen Netzwerkinterfaces an alle direkt angeschlossenen Computer (Broadcast).
	 * @param obj Das zu sendende Objekt
	 */
	public void broadcastObject(final Serializable obj) {
		try {
			final ByteArrayOutputStream baos = new ByteArrayOutputStream();
			final ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(obj);
			oos.close();
			final byte[] buf = baos.toByteArray();
			if (buf.length > this.RECEIVE_BUFFER_LENGTH) {
				// TODO: Problemfall: wir können nicht so viel empfangen, also
				// senden wir auch nicht zu viel
				System.err.println("Das Objekt ist zu groß zum Rundsenden");
			} else {
				for (final InetAddress broadcastAddr : this
						.getBroadcastAddresses()) {
					final DatagramPacket packet = new DatagramPacket(buf,
							buf.length);
					packet.setPort(this.COMMUNICATION_PORT);
					// packet.setAddress(InetAddress.getByName("255.255.255.255"));
					packet.setAddress(broadcastAddr);
					System.err.println("sent to " + broadcastAddr);
					this.socket.send(packet);
				}
			}
		} catch (final IOException e) {
			e.printStackTrace();
		}
	}

	public class IncomingObject {

		private final InetAddress from;
		private final Object data;

		private IncomingObject(final InetAddress from, final Object data) {
			this.from = from;
			this.data = data;
		}

		public InetAddress getAddress() {
			return this.from;
		}

		public Object getObject() {
			return this.data;
		}
	}

	public List<InetAddress> getBroadcastAddresses() {
		final ArrayList<InetAddress> ret = new ArrayList<InetAddress>();
		for (final InterfaceAddress interfaceAddr : this.broadcasterInterfaceAdresses) {
			ret.add(interfaceAddr.getBroadcast());
		}
		return ret;
	}

	/**
	 * Diese Methode liefert alle lokal vorhandenen IP-Adressen zurück.
	 * @return Eine Liste der IP-Adressen
	 */
	public List<InetAddress> getLocalAddresses() {
		final ArrayList<InetAddress> ret = new ArrayList<InetAddress>();
		for (final InterfaceAddress interfaceAddr : this.broadcasterInterfaceAdresses) {
			ret.add(interfaceAddr.getAddress());
		}
		return ret;
	}

	static ArrayList<InterfaceAddress> getBroadcastInterfaceAddresses() {
		final ArrayList<InterfaceAddress> ret = new ArrayList<InterfaceAddress>();
		try {
			final Enumeration<NetworkInterface> list = NetworkInterface
					.getNetworkInterfaces();
			while (list.hasMoreElements()) {
				final NetworkInterface iface = list.nextElement();
				if (iface == null) {
					continue;
				}
				if (!iface.isLoopback() && iface.isUp()) {
					for (final InterfaceAddress address : iface
							.getInterfaceAddresses()) {
						final InetAddress broadcast = address.getBroadcast();
						if (broadcast != null) {
							ret.add(address);
							System.err.println("Found address: " + address);
						} else {
							System.err.println("Ignoring interface:" + iface
									+ ", no broadcast address");
						}
					}
				} else {
					System.err.println("Ignoring interface:" + iface
							+ "loopback: " + iface.isLoopback() + ", up: "
							+ iface.isUp());
				}
			}
		} catch (final SocketException ex) {
			return new ArrayList<InterfaceAddress>();
		}
		return ret;
	}
}
